# -*- coding: utf-8 -*-
from interfaces import IFilter, IDict, IList
import zope.interface as zi

#{{{ utility methods
def ___is_list(l): # TODO: make check more broad
	""" checks if l is list(iterable and appendable)
	"""
	return l is not None and (isinstance(l,(list, tuple, set)) or IList.providedBy(l))

def ___is_dict(d):
	""" checks if d is dict-like object
	"""
	return d is not None and (isinstance(d,dict) or IDict.providedBy(d))

def ___update(dest, source):
	""" update dest dict from source dict. Only string keys are taken
	"""
	if not ___is_dict(dest) or not ___is_dict(source):
		return
	keys = filter(lambda x: isinstance(x, basestring), source.keys())
	for key in keys:
		x = source[key]
		if isinstance(x, basestring):
			dest[key] = x
#}}}
#{{{ Dict
# ----------------------v Dict v------------------------
class Dict(object):
	# XXX: strong type checking 
	"""
	self.dict - dictionary
	self.readonly - readonly properties
	self.allowed - allowed properties
	self.filters - dict of filters
	self.keymap - dict of mapped keys
	self."keymap.r" - reverse dict of mapped keys
	"""
	zi.implements (IDict,IFilter)
	def __init__(self, source=None):
		if source and ___is_dict(source):
			self.__dict__['dict']
		else:
			self.__dict__['dict'] = dict()

		self.__dict__['readonly'] = set([])
		self.__dict__['allowed'] = set([])
		self.__dict__['filters'] = dict()
		self.__dict__['keymap'] = dict()
		self.__dict__['keymap.r'] = dict()
	#{{{ Key modification
	def updateKeyMapping(self,E=None, **D):
		""" Update key modification mapping and reverse update.
			source mappings must be reversable
		"""
		keymap = self.__dict__['keymap']
		if E:
			___update(keymap, E)
		___update(keymap, D)
		keymap_r = self.__dict__['keymap.r']
		keymap_r.clear()
		drop = set([])
		for (key, data) in keymap.iteritems():
			if not ___is_str(key) or not ___is_str(data):
				drop.add(key)
				continue
			if data in keymap_r:
				raise KeyError("Can't build reverse mapping for '%s'" % data)
			keymap_r[data] = key
		for key in drop:
			del keymap[key]
	def realkey(self, key):
		""" Get real key from mapping
		"""
		if key and key in self.__dict__['keymap']:
			return self.__dict__['keymap'][key]
		else:
			return key
	#}}}
	#{{{ Allowed & readonly
	def addAllowed(self, *keys):
		""" Mark specified keys as allowed
		"""
		if keys:
			self.__dict__['allowed'].update(keys)

	def delAllowed(self,*keys):
		""" Mark specified keys as not allowed
		"""
		if keys:
			self.delReadonly(keys)
			for key in keys:
				del self[key]
			self.__dict__['allowed'].difference_update(keys)

	def addReadonly(self,*keys):
		""" Mark keys as read only
		"""
		if keys:
			# only allowed keys possible to mark as readonly
			k = set(keys)
			k.intersection_update(self.__dict__['allowed']) 
			self.__dict__['readonly'].update(k)

	def delReadonly(self,*keys):
		""" Mark specified keys as modifiable
		"""
		if keys:
			self.__dict__['readonly'].difference_update(keys)

	def keyAllowed(self, key):
		""" Checks if key allowed here
		"""
		if not self.keyValid(key):
			return False
		if self.__dict__['allowed']:
			strkey = key
			if type(key) is tuple:
				strkey = key[0]
			return key in self.__dict__['allowed']
		else:
			return True

	def keyReadonly(self, key):
		""" Checks if key read only
		"""
		return key in self.__dict__['readonly']

	def keyValid(self, key):
		""" Checks if key is valid
			if key is tuple:
				any item in tuple must be instance of basestring and 
				has any nonwhitespace characters
		"""
		if not key:
			return False
		if isinstance(key, basestring):
			return True
		if isinstance(key, tuple):
			return len(filter(lambda x:not isinstance(x, basestring), key))==0\
					and len(filter(None, map (lambda x:x.strip(),key))) == len(key)

	#}}}
	#{{{ implementation of IItemMapping

	def __getitem__(self, key):
		""" Gets an item from dict
		"""
		if not self.keyValid(key):
			raise KeyError('Key must be not empty string, unicode or tuple')

		if type(key) == tuple:
			a = self[key[0]]
			if a is None:
				return a
			elif len(key) == 1:
				return a
			else:
				return a[key[1:]]

		elif key in self:
			return self.filterOut(key, self.__dict__['dict'][self.realkey(key)])
		else:
			return None

	#}}}	
	#{{{ implementation of IReadMapping
	def get(self, key, default=None):
		""" Gets an item from dict and returns default if key not found
		"""
		data = self[key]
		if data is None:
			return default
		return data

	def __contains__(self, key):
		""" It is faster then in keys()
		"""
		return self.keyValid(key) \
				and self.keyAllowed(key) \
				and self.realkey(key) in self.__dict__['dict']

	#}}}
	#{{{ implementation of IWriteMapping
	def __setitem__(self, key, data):
		""" Sets an item in dict 
		"""
		if not self.keyValid(key):
			raise KeyError('Key must be not empty string, unicode or tuple')

		if not self.keyAllowed(key):
			return

		if type(key) == tuple:
			if len(key) == 1:
				self[key[0]] = data
			else:
				a = self[key[0]]
				if a is None:
					self[key[0]] = self.newValue(key[0])
					a = self[key[0]]
				if a is not None:	
					a[key[1:]] = data
			return

		real = self.realkey(key)
		d = self.filterInto(key, data)

		if not self.keyReadonly(key):
			if d is not None:
				self.__dict__['dict'][real] = d
		else:
			___update(self.__dict__['dict'][real], d)

	def __delitem__(self, key):
		""" Removes an item from dict
		"""
		if not self.keyValid(key):
			raise KeyError('Key must be not empty string, unicode or tuple')

		if not self.keyAllowed(key) or self.keyReadonly(key):
			return

		del self.__dict__['dict'][self.realkey(key)]

	#}}}
	#{{{ implementation of IEnumerableMapping
	def __len__(self):
		""" Returns actual length of dict
		"""
		return len(self.keys())

	def __iter__(self):
		""" Returns iterator over keys
		"""
		return iter(self.keys())

	def keys(self):
		""" Checks all actual keys in dict using allowed keys and mappings
		"""
		keymap_r = self.__dict__['keymap.r']
		keys = set(self.__dict__['dict'].keys())

		if keymap_r: # replace all keys to "unreal"
			keys_r = filter(lambda x: x in keymap_r, keys)
			keys.difference_update(keys_r)
			keys.update(map(lambda x: keymap_r[x], keys_r))

		return list(filter(self.keyAllowed, keys))

	def values(self):
		""" Returns all values from dict
		"""
		result = []
		for key in self.keys():
			result.append(self[key])
		return result

	def items(self):
		""" Returns all key-value pairs from dict
		"""
		result = []
		for key in self.keys():
			result.append((key,self[key]))
		return result

	#}}}
	#{{{ implementation of IIterableMapping
	def iterkeys(self):
		""" Direct iterator over keys. like iter() builtin
		"""
		return iter(self)

	def itervalues(self):
		""" Direct iterator over values. like iter() builtin
		"""
		return iter(self.values())

	def iteritems(self):
		""" Direct iterator over items. like iter() builtin
		"""
		return iter(self.items())

	#}}}
	#{{{ implementation of IDict
	def clear(self):
		""" Remove all keys from self
		"""
		for key in keys():
			del self[key]

	def update(self, E= None, **D):
		""" Update self from E and D mappings
		"""
		if E: ___update(self, E)
		___update(self, D)

	def createValue(self, key):
		""" Creates value for given key and put it in right place
		"""
		if key not in self:
			self[key] = self.newValue(key)

	def ___newValue(self, key):
		""" Real creation of new value
		"""
		return None

	def newValue(self,key):
		""" Creates and returns new value for given key. self[key] stays untouched
		"""
		if not self.keyValid(key):
			raise KeyError('Key must be not empty string, unicode or tuple')
		if self.keyAllowed(key):
			src = self.__dict__['dict']
			if IDict.providedBy(src):
				return src.newValue(self.realkey(key))
			return self.___newValue(key)
		return None

	#}}}
	#{{{ implementation of IFilter
	def __addDataFilter(self, key, filter):
		""" Adds data filter for given key. this is [Protected] method
		"""
		if IFilter.providedBy(filter):
			self.__dict__['filters'][key] = filter

	def filterInto(self, key, data):
		""" Filter data for given key
		"""
		if not key in self.__dict__['filters']:
			return data
		return self.__dict__['filters'][key].filterInto(key, data)

	def filterOut(self, key, data):
		""" Filter data for given key
		"""
		if not key in self.__dict__['filters']:
			return data
		return self.__dict__['filters'][key].filterOut(key, data)

	#}}}
# ----------------------^ Dict ^------------------------
#}}}
#{{{ List
# ----------------------v List v------------------------
class List(object):
	'''
		self.list - actual list
		self.filter - data filter (should implements IFilter
		self.generator - fabric for dictionary values
	'''
	zi.implements(IList, IFilter)
	def __init__(self, generator = None, key = None):
		self.__dict__['list'] = list()
		self.__dict__['filter'] = None
		if not callable(generator):
			key = None
			generator = None
		self.__dict__['generator'] = generator
		self.__dict__['key'] = key

	#{{{ IObjectList implementation
	def __iter__(self):
		""" Iterate over values
		"""
		f = lambda x: self.filterOut(None, x)
		return iter(map(f, self.__dict__['list']))

	def __len__(self):
		""" length of self collection
		"""
		return len(self.__dict__['list'])

	def append(self, data):
		""" Append value with data
		"""
		a = self.filterInto(self.__dict__['key'], data)
		if a:
			self.__dict__['list'].append(a)
		return a

	#}}}
	#{{{ IFilter implementation
	def filterInto(self, key, data):
		generator = self.__dict__['generator']
		if generator is not None:
			dict = generator()
			if not ___is_dict(dict):
				raise TypeError("%s objects are not accepted" % str(type(dict)))
			if data:
				dict [self.__dict__['key']] = data
			data = dict

		if self.__dict__['filter'] and self.__dict__['filter'] is not self:
			return self.__dict__['filter'].filterInto(data)
		return data

	def filterOut(self, key, data):
		if self.__dict__['filter'] and self.__dict__['filter'] is not self:
			return self.__dict__['filter'].filterOut(data)
		return data
	#}}}
# ----------------------^ List ^------------------------
#}}}
#{{{ FilterStack
# -------------------v FilterStack v--------------------
class FilterStack(object):
	zi.implements(IFilter)
	def __init__(self, *filters):
		self.__dict__['filters'] = list()
		for f in filters:
			self.__dict__['filters'].append(f)

	def filterInto(self, key, data):
		d = data
		for f in self.__dict__['filters']:
			d = f.filterInto(key, d)
			if d is None:
				return None
		return d

	def filterOut(self, key, data):
		d = data
		for f in self.__dict__['filters']:
			d = f.filterOut(key, d)
			if d is None:
				return None
		return d
# -------------------^ FilterStack ^--------------------
#}}}	

del zi
